﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Seagull.Internal
{

    internal class EntityDictionaryJsonConverter<TEntity> : JsonConverter<EntityDictionary<TEntity>> where TEntity : class
    {
        private readonly JsonSerializerOptions options;

        public EntityDictionaryJsonConverter(JsonSerializerOptions options)
        {
            this.options = options;
        }

        public override EntityDictionary<TEntity> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }
            var properties = typeof(TEntity).GetProperties();
            var dictionary = new EntityDictionary<TEntity>();
            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndObject)
                {
                    return dictionary;
                }

                // Get the key.
                if (reader.TokenType != JsonTokenType.PropertyName)
                {
                    throw new JsonException();
                }

                string propertyName = reader.GetString()!;
                reader.Read();
                var property = properties.FirstOrDefault(x => string.Equals(x.Name, propertyName, StringComparison.OrdinalIgnoreCase));
                if (property is not null && property.CanWrite)
                {
                    if (property.GetCustomAttribute<JsonIgnoreAttribute>() is not null)
                        continue;
                    var itemOptions = new JsonSerializerOptions(options);
                    var converters = property.GetCustomAttributes<JsonConverterAttribute>();
                    foreach (var converter in converters)
                    {
                        if (converter.ConverterType is null)
                            continue;
                        object? instance = App._provider.GetService(converter.ConverterType);
                        if (instance is null)
                        {
                            var ctor = converter.ConverterType.GetConstructor(Array.Empty<Type>());
                            if (ctor is null)
                                continue;
                            instance = ctor.Invoke(null);
                        }
                        itemOptions.Converters.Add((JsonConverter)instance);
                    }
                    var value = JsonSerializer.Deserialize(ref reader, property.PropertyType, itemOptions);
                    dictionary.Replace(property.Name, value);
                }
            }
            throw new JsonException();
        }

        public override void Write(Utf8JsonWriter writer, EntityDictionary<TEntity> dictionary, JsonSerializerOptions options)
        {
            var properties = typeof(TEntity).GetProperties();
            writer.WriteStartObject();
            foreach (var item in dictionary)
            {
                var propertyName = item.Key;
                var property = properties.FirstOrDefault(x => string.Equals(x.Name, propertyName, StringComparison.OrdinalIgnoreCase));
                if (property is not null && property.CanRead)
                {
                    var value = item.Value;
                    if (property.GetCustomAttribute<JsonIgnoreAttribute>() is not null)
                        continue;
                    var itemOptions = new JsonSerializerOptions(options);
                    var converters = property.GetCustomAttributes<JsonConverterAttribute>();
                    foreach (var converter in converters)
                    {
                        if (converter.ConverterType is null)
                            continue;
                        object? instance = App._provider.GetService(converter.ConverterType);
                        if (instance is null)
                        {
                            var ctor = converter.ConverterType.GetConstructor(Array.Empty<Type>());
                            if (ctor is null)
                                continue;
                            instance = ctor.Invoke(null);
                        }
                        itemOptions.Converters.Add((JsonConverter)instance);
                    }
                    writer.WritePropertyName(options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);
                    JsonSerializer.Serialize(writer, value, itemOptions);
                }   
            }
            writer.WriteEndObject();
        }
    }
}